﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using Amazon;
using Amazon.CloudWatch;
using Amazon.CloudWatch.Model;
using Web_App___AWS_ELB_Monitor.Entities;
using Web_App___AWS_ELB_Monitor;

namespace Web_App___AWS_ELB_Monitor
{
    public class CloudWatchDataHelper
    {
        private const string CW_EU_ServiceUrl = "https://eu-west-1.monitoring.amazonaws.com";

        private static AmazonCloudWatch CloudWatchClient
        {
            get
            {
                return AWSClientFactory.CreateAmazonCloudWatchClient(CustomValueHelper.GetProperty("AWSAccessKey"), CustomValueHelper.GetProperty("AWSSecretKey"), new AmazonCloudWatchConfig() { ServiceURL = CW_EU_ServiceUrl });
            }
        }



        public static IEnumerable<DataPoint> Get24HrPeriodCPUCountForServer(string instanceId, DateTimeOffset day, bool onlyTime, AnalyticsValueType valueType)
        {
            return Get24HourPeriodEC2InstanceCount(instanceId, day, onlyTime, valueType, CloudWatchDefaultMetricEC2.CPUUtilization, "Percent");
        }

        public static IEnumerable<DataPoint> Get24HourPeriodMetricAlarm(MetricAlarm alarm, DateTimeOffset day, bool onlyTime = true)
        {
            //reset date to beginning of its day
            DateTimeOffset start = day.UtcDateTime.Date;

            //build cloud watch request
            var cwReq = new GetMetricStatisticsRequest()
                    .WithDimensions(alarm.Dimensions)
                    .WithMetricName(alarm.MetricName)
                    .WithNamespace(alarm.Namespace)
                    .WithPeriod(alarm.Period)
                    .WithStatistics(alarm.Statistic)
                    .WithUnit(alarm.Unit)
                    .WithStartTime(start.UtcDateTime) //from
                    .WithEndTime(start.AddDays(01).UtcDateTime) //until
                ;
            var stats = CloudWatchClient.GetMetricStatistics(cwReq);

            AnalyticsValueType valueType;
            Enum.TryParse<AnalyticsValueType>(alarm.Statistic, out valueType);

            return stats.ToDataPoints(onlyTime, valueType);
        }

        public static IEnumerable<DataPoint> Get24HourPeriodEC2InstanceCount(string instanceId, DateTimeOffset day, bool onlyTime, AnalyticsValueType valueType, CloudWatchDefaultMetricEC2 metric, string unit)
        {
            //reset date to beginning of its day
            DateTimeOffset start = day.UtcDateTime.Date;

            //send request to cloud watch
            var stats = CloudWatchClient.GetMetricStatistics(
                new GetMetricStatisticsRequest()
                    .WithUnit(unit)
                    .WithStatistics(new[] { valueType.ToString() }) //get me the average
                    .WithStartTime(start.UtcDateTime) //from
                    .WithEndTime(start.AddDays(01).UtcDateTime) //until
                    .WithDimensions(
                        new Dimension().WithName(CloudWatchDefaultDimensionsEC2.InstanceId.ToString()).WithValue(
                            instanceId)
                    )
                    .WithMetricName(metric.ToString()) //for the cpu usage
                    .WithPeriod(600) //per x seconds
                    .WithNamespace("AWS/EC2")
                );

            //parse results to our dated data
            return stats.ToDataPoints(onlyTime, valueType);
        }

        public static DataPoint GetEC2InstanceSingleMetric(string instanceId, DateTimeOffset start, DateTimeOffset end, AnalyticsValueType valueType, CloudWatchDefaultMetricEC2 metric, string unit)
        {
            var req = new GetMetricStatisticsRequest()
                          {
                              Dimensions =
                                  new List<Dimension>
                                      {
                                          new Dimension
                                              {
                                                  Name = CloudWatchDefaultDimensionsEC2.InstanceId.ToString(),
                                                  Value = instanceId
                                              }
                                      },
                              MetricName = metric.ToString(),
                              Statistics = new List<String> { valueType.ToString() },
                              Period = Convert.ToInt32(end.Subtract(start).TotalMinutes * 60),
                              Unit = unit,
                              Namespace = "AWS/EC2",
                              StartTime = start.UtcDateTime,
                              EndTime = end.UtcDateTime
                          };

            return CloudWatchClient.GetMetricStatistics(req).ToDataPoints(false, valueType).FirstOrDefault();
        }

        public static DataPoint GetEC2InstancesInASGSingleMetric(string autoScalingGroupName, DateTimeOffset start, DateTimeOffset end, AnalyticsValueType valueType, CloudWatchDefaultMetricEC2 metric, string unit)
        {
            return GetMetricsForEC2InstancesInASG(autoScalingGroupName, start, end, valueType, metric, unit).FirstOrDefault();
        }

        public static IEnumerable<DataPoint> GetMetricsForEC2InstancesInASG(string autoScalingGroupName, DateTimeOffset start, DateTimeOffset end, AnalyticsValueType valueType, CloudWatchDefaultMetricEC2 metric, string unit, int? periodSeconds = null)
        {
            var req = new GetMetricStatisticsRequest()
            {
                Dimensions =
                    new List<Dimension>
                                      {
                                          new Dimension
                                              {
                                                  Name = CloudWatchDefaultDimensionsEC2.AutoScalingGroupName.ToString(),
                                                  Value = autoScalingGroupName
                                              }
                                      },
                MetricName = metric.ToString(),
                Statistics = new List<String> { valueType.ToString() },
                Period = periodSeconds ?? Convert.ToInt32(end.Subtract(start).TotalMinutes * 60),
                Unit = unit,
                Namespace = "AWS/EC2",
                StartTime = start.UtcDateTime,
                EndTime = end.UtcDateTime
            };

            return CloudWatchClient.GetMetricStatistics(req).ToDataPoints(false, valueType);
        }

        public static IEnumerable<DataPoint> GetMetricsForEC2Instance(string instanceName, DateTimeOffset start, DateTimeOffset end, AnalyticsValueType valueType, CloudWatchDefaultMetricEC2 metric, string unit, int? periodSeconds = null)
        {
            var req = new GetMetricStatisticsRequest()
            {
                Dimensions =
                    new List<Dimension>
                                      {
                                          new Dimension
                                              {
                                                  Name = CloudWatchDefaultDimensionsEC2.InstanceId.ToString(),
                                                  Value = instanceName
                                              }
                                      },
                MetricName = metric.ToString(),
                Statistics = new List<String> { valueType.ToString() },
                Period = periodSeconds ?? Convert.ToInt32(end.Subtract(start).TotalMinutes * 60),
                Unit = unit,
                Namespace = "AWS/EC2",
                StartTime = start.UtcDateTime,
                EndTime = end.UtcDateTime
            };

            var stats = CloudWatchClient.GetMetricStatistics(req);
            return stats.ToDataPoints(false, valueType).ToList();
        }

        public static IEnumerable<IEnumerable<DataPoint>> GetLoadBalancerDataMultipleTimeSpans(string loadBalancerName, params DateTimeOffset[] days)
        {
            return days.Select<DateTimeOffset, IEnumerable<DataPoint>>(day => Get24HrPeriodRequestCountForElb(loadBalancerName, day, true, AnalyticsValueType.Average));
        }

        public static IEnumerable<IEnumerable<DataPoint>> GetAlarmDataMultipleTimeSpans(MetricAlarm alarm, params DateTimeOffset[] days)
        {
            return days.Select<DateTimeOffset, IEnumerable<DataPoint>>(day => Get24HourPeriodMetricAlarm(alarm, day, true));
        }

        public static IEnumerable<IEnumerable<DataPoint>> GetCPUDataMultipleTimeSpans(string instanceId, params DateTimeOffset[] days)
        {
            return days.Select(day => Get24HrPeriodCPUCountForServer(instanceId, day, true, AnalyticsValueType.Average));
        }

        public static IEnumerable<IEnumerable<DataPoint>> GetCPUDataMultipleTimeSpans(string instanceId, CloudWatchDefaultMetricEC2 metric, string unit, params DateTimeOffset[] days)
        {
            return days.Select(day => Get24HourPeriodEC2InstanceCount(instanceId, day, true, AnalyticsValueType.Average, metric, unit));
        }

        public static IEnumerable<DataPoint> Get24HrPeriodRequestCountForElb(string loadBalancerName, DateTimeOffset? day, bool onlyTime, AnalyticsValueType valueType)
        {
            int period = 600;

            DateTimeOffset start, end;
            if (day.HasValue)
            {
                start = day.Value.UtcDateTime.Date;
                end = start.AddDays(01);
            }
            else
            {
                end = DateTimeOffset.UtcNow;
                start = end.AddDays(-01);
            }

            // Cap off at last completed time period. Without cap, the current stats always appear less than they should. 
            // E.g. if the time is 9:21, then the stats for today grouped as "9:20" would only include 9:20-9:21 for today, but would include 9:20-9:29 for previous days. Thus making toiday
            //if (end > DateTimeOffset.UtcNow && start == DateTimeOffset.UtcNow.Date && period != 0)
            //{
            //    end = start.AddSeconds(Math.Floor((DateTimeOffset.UtcNow - start).TotalSeconds / period) * period);
            //}

            // Convert to UniversalTime, as AWS uses UniversalTime from what I can see.
            start = start.ToUniversalTime();
            end = end.ToUniversalTime();

            var statRequest =
                new GetMetricStatisticsRequest()
                    .WithStatistics(new[] { "Sum" }) //get me the sum
                    .WithStartTime(start.UtcDateTime) //from start
                    .WithEndTime(end.UtcDateTime); //until now
            if (!String.IsNullOrWhiteSpace(loadBalancerName))
            {
                statRequest = statRequest.WithDimensions(  //with load-balancer name x as dimension
                    new Dimension().WithName(CloudWatchDefaultDimensionsELB.LoadBalancerName.ToString()).WithValue(loadBalancerName)
                    );
            }

            statRequest = statRequest.WithMetricName(CloudWatchDefaultMetricELB.RequestCount.ToString()) //for the request count
                .WithPeriod(period) //per x seconds
                .WithNamespace("AWS/ELB");
            var stats = CloudWatchClient.GetMetricStatistics(statRequest);

            return stats.ToDataPoints(onlyTime, AnalyticsValueType.Sum);

        }

        public static IEnumerable<DataPoint> Get24HrPeriodDesiredCapacityCountForASG(string autoScalingGroupName, DateTimeOffset? day = null, bool onlyTime = true)
        {
            int period = 60;
            DateTimeOffset start, end;
            if (day.HasValue)
            {
                start = day.Value.UtcDateTime.Date;
                end = start.AddDays(01);
            }
            else
            {
                start = DateTimeOffset.UtcNow.UtcDateTime.Date;
                end = start.AddDays(01);
            }


            var statRequest = new GetMetricStatisticsRequest()
                .WithStatistics(new[] { "Maximum" }) //get me the sum
                .WithStartTime(start.UtcDateTime) //from start time
                .WithEndTime(end.UtcDateTime) //until end time
                .WithDimensions( //with asg name x as dimension
                new Dimension().WithName(CloudWatchDefaultDimensionsEC2.AutoScalingGroupName.ToString()).WithValue(autoScalingGroupName)
                )
                .WithMetricName("GroupDesiredCapacity") //for how many instances we have desired
                .WithPeriod(period) //per x seconds
                .WithNamespace("AWS/AutoScaling");
            var stats = CloudWatchClient.GetMetricStatistics(statRequest);

            return stats.ToDataPoints(onlyTime, AnalyticsValueType.Maximum);
        }

        public static IEnumerable<MetricAlarm> GetAlarms()
        {
            return CloudWatchClient.DescribeAlarms(new DescribeAlarmsRequest()).DescribeAlarmsResult.MetricAlarms;
        }

        public static MetricAlarm GetAlarm(string Arn)
        {
            return CloudWatchClient.DescribeAlarms(new DescribeAlarmsRequest().WithAlarmNames(Arn)).DescribeAlarmsResult.MetricAlarms.FirstOrDefault();
        }
    }
}